/*
 * Matthew Cramer, Jesse Martinez, Marc Mendoza 
 * CSE 325 Lab 6
 * File: FileSystem.C
 * Description: 
 *
 */


#include "../QueueManager/QueueManager.h"

#define TOTAL_SPACE 65536

//There are 65,536 blocks when the hd is divided up
file_control_block hd[TOTAL_SPACE];
int head = -1;
int tail = -1;
int freeHead = -1;
int freeTail = TOTAL_SPACE-1;
int size = 0;

int getSizeOfHD();
file_control_block* getHardDrive();
//int bad_block(int block);
int getDirLoc(int dirId);
int getFileLoc(int dirLoc, int fileId);
int isOpen(int dirLoc, int fileId);
void initHD();
void format();
void mountfs();
void create_directory(int id);
int openFile(int dirId, int fileId, int perm);
int readBlock(int dirId, int fileId, int blockNum, int bufNum);
int writeBlock(int dirId, int fileId, int blockNum, int bufNum);
int closeFile(int dirId, int fileId);
int *deleteObjects(int dirId, int fileId);
int unmountfs();
int getSizeOfHD();


int getSizeOfHD()
{
    return size;
}

file_control_block* getHardDrive()
{
	return hd;
}

/*int bad_block(int block)
 {
 Create_Bad_Block(hd,block);
 return hd[10].data;
 }*/

//gets the location of specified directory
int getDirLoc(int dirId)
{
	int i = 0;
	for(i = 0; i < hd[267].ptrSize; i++)
	{
		if(hd[267].pointers[i].name == dirId)
			return hd[267].pointers[i].location;
	}
	
	//error for directory not found
	return -1;
}

int getFileLoc(int dirLoc, int fileId)
{
	int i = 0;
	for(i = 0; i < hd[dirLoc].ptrSize; i++)
	{
		if(hd[dirLoc].pointers[i].name == fileId)
			return hd[dirLoc].pointers[i].location;
	}
	
	//file not in directory
	return -1;
}

int isOpen(int dirLoc, int fileId)
{
	int i = 0;
	for(i = 0; i < hd[dirLoc].ptrSize; i++)
	{
		if(hd[dirLoc].pointers[i].name == fileId) {
			//is open and availble for writing and reading
			if((hd[dirLoc].pointers[i].valid == 0)
               && (hd[getFileLoc(dirLoc, fileId)].permission == 2))
				return 1;
			//is open and availble for reading
			else if((hd[dirLoc].pointers[i].valid == 0)
                    && (hd[getFileLoc(dirLoc, fileId)].permission == 1))
				return 0;
		}
	}
	
	//catch all error
	return -1;
}

//initializes the "hard disk"
void initHD()
{
	iniQueue(&head, &tail, &freeHead, &freeTail, &size, hd, 0, TOTAL_SPACE);
	iniFree(&freeHead, &freeTail, 0, hd);
	
	//dummy pointer array is made for the enqueue
	ptrBlock dummyBlock[1024];
	
	//enqueue the Buffer(0-9) and the bad blocks (10-265)
	int i = 0;
	for(i = 0; i < 266; i++)
	{
		Enqueue(0, 0, 0, -1, 0, dummyBlock, 0, 0, 0, 0, 
                &head, &tail, &freeHead, &freeTail, hd);
		size++;
	}
	
	//pointer is setup to point to filesystem header and is not reset for 
	//file system header because it will be overwritten when filesystem gets
	//directories/files
	dummyBlock[0].name = 0;
	dummyBlock[0].location = 267;
	dummyBlock[0].valid = 0;
	
	//Position 266 is the Device Head
	Enqueue(0, 4, 0, -1, 0, dummyBlock, 1, 0, 0, 0, 
			&head, &tail, &freeHead, &freeTail, hd);
	size++;
    
	//Position 267 is the File System Header, is created
	//to have an indicator that the file system is not formatted
	Enqueue(0, 5, 0, -1, 0, dummyBlock, 0, 0, 0, 0, 
			&head, &tail, &freeHead, &freeTail, hd);
	size++;
}

//bit is changed in file system header to indicate file system is formatted
void format()
{
	hd[267].fileSystem = 1;
}

//bit is changed in device header to indicate file system is mounted
void mountfs()
{
	hd[266].mounted = 1;
}

//a directory head is created and the file system head is updated to
//reflect this
void create_directory(int id)
{
	//a directory header is enqueued
	ptrBlock dummyBlock[1024];
	Enqueue(id, 1, 267, -1, 0, dummyBlock, 0, 0, 0, 0, 
			&head, &tail, &freeHead, &freeTail, hd);
    size++;
    
	//file system head is updated with a pointer to the new
	//directory head
	hd[267].pointers[hd[267].ptrSize].name = id;
	hd[267].pointers[hd[267].ptrSize].location = tail;
	hd[267].pointers[hd[267].ptrSize].valid = 1;
	hd[267].ptrSize++;
}

int openFile(int dirId, int fileId, int perm)
{
	int dirLoc = getDirLoc(dirId);
	int fileLoc = getFileLoc(dirLoc, fileId);
	
	//file not found, create a new file header
	if(fileLoc == -1)
	{
		//file block is enqueued onto the hd
		ptrBlock dummyBlock[1024];
		Enqueue(fileId, 2, dirLoc, -1, 0, dummyBlock, 0, 0, 0, 0, 
				&head, &tail, &freeHead, &freeTail, hd);
		size++;
		
		//file header is updated with the new block id and location
		hd[dirLoc].pointers[hd[dirLoc].ptrSize].name = fileId;
		hd[dirLoc].pointers[hd[dirLoc].ptrSize].location = tail;
		hd[dirLoc].pointers[hd[dirLoc].ptrSize].valid = 1;
		hd[dirLoc].ptrSize++;
		
		return 1024;
	}
	
	else
	{
		int i = 0;
		for(i = 0; i < hd[dirLoc].ptrSize; i++)
		{
			if(hd[dirLoc].pointers[i].name == fileId) {				
				//bits are changed to indicate open file with proper permissions
				hd[dirLoc].pointers[i].valid = 0;
				hd[fileLoc].permission = perm;
				return 1024;
			}
		}
	}
	
	return -1;
}

//return -1 if no data in block for file
//return -2 if file not open to read
//return -3 if data block does not exist
//return 0 if successful
int readBlock(int dirId, int fileId, int blockNum, int bufNum)
{
	int dirLoc = getDirLoc(dirId);
	int fileLoc = getFileLoc(dirLoc, fileId);
    
	
    //File not found
    if(fileLoc == -1)
		return -3;
	
	//if the file is not open, return an error
	if(isOpen(dirLoc, fileId) == -1)
		return -2;
	//if no data to read
	if(hd[fileLoc].ptrSize == 0)	
		return -1;
    
    
	int i = 0, exist = 0;
	for(i = 0; i < hd[fileLoc].ptrSize; i++)
	{
		if(hd[fileLoc].pointers[i].name == blockNum)
		{
			/*isBad(hd,hd[fileLoc].pointers[i].location,size);
			if(hd[10].data ==5)
			{
				return -5;
				
			}
			else if(hd[10].data == -4)
			{
				return -6;
			}*/
			exist = 1;
		}
	}
	
	if(exist != 1)
		return -4;
    
    
	//file block is written to specified buffer
	hd[bufNum].ID = hd[fileLoc].ID;
	hd[bufNum].parentPtr = fileLoc;
    
	return 0;
}

//A block of data is written to a file and a buffer
int writeBlock(int dirId, int fileId, int blockNum, int bufNum)
{
	int dirLoc = getDirLoc(dirId);
	int fileLoc = getFileLoc(dirLoc, fileId);
    
    //File not found
    if(fileLoc == -1)
		return -3;
	
	//if the file is not open and has the right permission, return an error
	if(isOpen(dirLoc, fileId) != 1)
		return -1;
    
    
    int i = 0, exist = 0;
	for(i = 0; i < hd[fileLoc].ptrSize; i++)
	{
		if(hd[fileLoc].pointers[i].name == blockNum)
        {
			exist = 1;
            //file block is written to specified buffer
            hd[bufNum].ID = blockNum;
            hd[bufNum].parentPtr = fileLoc;
            return -4;
        }
	}
	
    //file block is written to specified buffer
	hd[bufNum].ID = blockNum;
	hd[bufNum].parentPtr = fileLoc;
    
	//file block is enqueued onto the hd
	ptrBlock dummyBlock[1024];
	Enqueue(hd[bufNum].ID, 3, hd[bufNum].parentPtr, -1, 0, dummyBlock, 0, 0, 0, 0, 
			&head, &tail, &freeHead, &freeTail, hd);
	size++;
	
	//file header is updated with the new block id and location
	hd[fileLoc].pointers[hd[fileLoc].ptrSize].name = blockNum;
	hd[fileLoc].pointers[hd[fileLoc].ptrSize].location = tail;
	hd[fileLoc].pointers[hd[fileLoc].ptrSize].valid = 1;
	hd[fileLoc].ptrSize++;
	
	return 0;
}

//Error Cases:
//return -1 file not in given directory
//return -2 file not opened
int closeFile(int dirId, int fileId)
{
	int dirLoc = getDirLoc(dirId);
	int fileLoc = getFileLoc(dirLoc, fileId);
    
	if(fileLoc == -1)
		return -1;
    
	int i = 0;
	for(i = 0; i < hd[dirLoc].ptrSize; i++)
	{
		if(hd[dirLoc].pointers[i].name == fileId) {
			if(hd[dirLoc].pointers[i].valid == 1)
				return -2;
			
			hd[dirLoc].pointers[i].valid = 1;
			hd[fileLoc].permission = 0;
			return 0;
		}
	}
	
	return -1;
}

//return -1 file does not exist in directory or could not delete directory and its files
//return -2 if a file is open
//if fileId is -1, recursively delete directory
int* deleteObjects(int dirId, int fileId)
{
	int returnArray[60];
	int x;
	for(x=0;x<60;x++)
	{
		returnArray[x]=-6;
	}
    
	int dirLoc = getDirLoc(dirId);
	//if a single file is to be deleted
    
	if(fileId != -1) {
        
		int fileLoc = getFileLoc(dirLoc, fileId);
        
		//error file does not exist
		if(fileLoc == -1)
		{
			returnArray[0]=-1;
			return returnArray;
		}
        
		//error file is open
		if(hd[fileLoc].permission != 0)
		{
			returnArray[0]=-2;
            returnArray[1]=hd[fileLoc].ID;
			return returnArray;
		}
        
		//loop through and delete files data block
		int i = 0;
		for(i=0; i<hd[fileLoc].ptrSize; i++) {
			Delete(hd[fileLoc].pointers[i].location, &head, &tail, &freeHead, &freeTail, hd);
			size--;
		}
		//delete file header
		returnArray[0]=hd[fileLoc].ID;
		Delete(fileLoc, &head, &tail, &freeHead, &freeTail, hd);
		
		
		size--;
		
		//delete pointer to file header in the directory header
		int found = 0;
		i = 0;
        
		for(i=0; i<hd[dirLoc].ptrSize-1; i++) {
			if((found == 0) && (hd[dirLoc].pointers[i].name == fileId)) {
				found = 1;
			}
			
			if(found == 1) {
				hd[dirLoc].pointers[i].name = hd[dirLoc].pointers[i+1].name;
				hd[dirLoc].pointers[i].location = hd[dirLoc].pointers[i+1].location;
				hd[dirLoc].pointers[i].valid = hd[dirLoc].pointers[i+1].valid;
			}
		}
		hd[dirLoc].ptrSize--;
		
		return returnArray;
	}
	
	//a directory is to be recursively deleted
	else {
		//loop through to check if any files are open and return error if there is
		int i = 0;
		for(i = 0; i < hd[dirLoc].ptrSize; i++)
		{
			if(hd[dirLoc].pointers[i].valid == 0)
			{
                returnArray[0]=-2;
                returnArray[1]=hd[dirLoc].pointers[i].name;
                return returnArray;
			}
		}
		
		//recursively delete each file then the directory itself
		i = 0;
		for(i = 0; i < hd[dirLoc].ptrSize; i++)
		{
			int j = 0;
			int fileLoc	= hd[dirLoc].pointers[i].location;
			//delete file data blocks
			for(j = 0; j < hd[fileLoc].ptrSize; j++)
			{
				Delete(hd[fileLoc].pointers[j].location, &head, &tail, &freeHead, &freeTail, hd);
				
				size--;
			}
			//delete file header
			returnArray[i]=hd[fileLoc].ID;
			Delete(fileLoc, &head, &tail, &freeHead, &freeTail, hd);
			
			size--;
		}
		//delete directory
		Delete(dirLoc, &head, &tail, &freeHead, &freeTail, hd);
		
		size--;
		
		//remove pointer to directory in the file system header
		int found = 0;
		i = 0;
		for(i=0; i<hd[267].ptrSize-1; i++) {
			if((found == 0) && (hd[267].pointers[i].name == dirId))
				found = 1;
			
			if(found == 1) {
				hd[267].pointers[i].name = hd[267].pointers[i+1].name;
				hd[267].pointers[i].location = hd[267].pointers[i+1].location;
				hd[267].pointers[i].valid = hd[267].pointers[i+1].valid;
			}
		}
		hd[267].ptrSize--;
		
		return returnArray;
	}
	returnArray[0]=-1;
	return returnArray;
}

//device head mounted bit is set to 0 if the system is mounted
//else -1 is returned if file system already unmounted
int unmountfs()
{
	if(hd[266].mounted == 1)
	{
		hd[266].mounted = 0;
		return 0;
	}
	
	else
		return -1;
}